/**
 * Framework for batch testing user profile models
 * Final project by Sergey Nepomnyachiy and Julia Polchin
 * Supervisor: Tsvi Kuflik
 *
 */

package core;

import interfaces.ComponentInterface;
import interfaces.LoggingInterface;
import interfaces.GeneratorInterface;
import interfaces.ProgressKeeperInterface;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import java.util.Vector;


import core.environment.Defines;
import core.environment.fVector;

/**
 * This class encapsulates both the method and arguments generator
 * @author Sergey and Julia
 * 
 */
public class MethodCapsule implements ComponentInterface {

	/**
	 * Describing the 4 different invocation targets for the encapsulated method 
	 */
	public enum Cases { 
		/**
		 * single argument and a static method
		 */
		STATIC_SINGLE_ARG, 
		/**
		 * multiple argument and a static method
		 */
		STATIC_MULT_ARG,
		/**
		 * single argument and a static method but the arg is a collection
		 */
		STATIC_SINGLE_VECTOR, 		
		/**
		 * single argument and a object method
		 */
		OBJ_SINGLE_ARG, 
		/**
		 * multi argument and a object method
		 */
		OBJ_MULT_ARG,
		/**
		 * single argument and an object method but the arg is a collection
		 */
		OBJ_SINGLE_VECTOR
	}

	private Cases inTargetType;
	/**
	 * FileSystem handler - we need this pointer to backend because the logging directions 
	 * are in the <method> element
	 */
	@SuppressWarnings("unused")
	private LoggingInterface fs;
	/**
	 * Points whether to log the output of this method as "right"
	 */
	private final boolean isRight;
	/**
	 * Points which argument of this method to log as "left"
	 * when left == -1 - no logging is done at all
	 */
	private final int left;
	/** 
	 * Reference to reflect.method. Null in case of dummy method declaration
	 */
	private Method method;
	/**
	 * Reference to object we are going to invoke the method at. 
	 * Null in case of static method
	 */
	private Object instance;
	private GeneratorInterface<?> gen;
	private Collection<?> generated;
	/**
	 * single iterator - used for static methods
	 */
	private Iterator<?> iterator;
	/**
	 *  vector of iterators - used for instance methods
	 */
	private Vector<Iterator<?>> iteratorsVector;


	/**
	 * MethodCapsule constructor
	 * @param fs FileSystem handler - we need this pointer to backend for logging
	 * @param mtd The method to be invoked
	 * @param gen Generator of values-arguments
	 * @param left Which argument (if any ) to log as observed?
	 * @param isRight Shall we store return value as predicted?
	 */
	public MethodCapsule(LoggingInterface fs, Method mtd,
			GeneratorInterface<?> gen, int left, boolean isRight) {
		this.fs = fs;
		// this.isStatic = isStatic;
		this.method = mtd;
		this.gen = gen;
		this.left = left;
		this.isRight = isRight;
		this.generated = null;
		this.instance = null;
		this.iteratorsVector = new Vector<Iterator<?>> ();
		setArgsType();
	}

	/**
	 * Executes exactly one call to method 
	 * @param obj argument/s for the call
	 */
	private void execOne(Object obj) {
		Object[] args = getArgsForObj(obj);

		if (this.left > -1) {
			Object observed = args[this.left]; 
			//sanity check - should be arithmetic type (also unwrap arrays, vectors)
			if (observed instanceof Object[])
				observed = ((Object[]) observed)[0];
			if (observed instanceof Vector)
				observed = ((Vector) observed).elementAt(0);
			if (observed instanceof Integer || observed instanceof Double)
				UserObservedPredictedHolder.getInstance().addObserved(observed);
			else 
				UserObservedPredictedHolder.getInstance().addObserved(Double.NaN);
		}

		if ( this.method == null) //if it was a dummy - do nothing
			return;

		try {
			if (this.isRight)
				UserObservedPredictedHolder.getInstance().addPredicted(this.method.invoke(this.instance, args));
			else
				this.method.invoke(this.instance, args);

		} catch (IllegalArgumentException e) {
			Defines.printException("In MethodCapsule " + e.getMessage());
		} catch (IllegalAccessException e) {
			Defines.printException("In MethodCapsule " + e.getMessage());
		} catch (InvocationTargetException e) {
			Defines.printException("In MethodCapsule " + e.getMessage());
		}



	}

	@SuppressWarnings("unchecked")
	private Object[] 	getArgsForObj(Object obj) {

		Object[] wrapped = { obj };
		Object[] args = wrapped; //the default case

		if ( this.inTargetType == Cases.STATIC_MULT_ARG ||
				this.inTargetType == Cases.OBJ_MULT_ARG) {
			args = ((Vector<Object>) obj).toArray();
		}

		if ( this.inTargetType == Cases.STATIC_SINGLE_VECTOR ||
				this.inTargetType == Cases.OBJ_SINGLE_VECTOR) {
			if ( ! obj.getClass().equals(fVector.class)) //the arg is not fVector typed - must convert
				args = convertToFVector(obj);
		}
		return args;
	}
	
	/**
	 * Lazy initialization method
	 */
	private void testAndInit() {
		if (this.generated == null) {
			this.generated = this.gen.getVector();
			this.iterator = this.generated.iterator();
			//if this is a method encapsulated within "users" - it gets one more dimension
			if ( this.inTargetType == Cases.OBJ_MULT_ARG || this.inTargetType == Cases.OBJ_SINGLE_VECTOR ||
					this.inTargetType == Cases.OBJ_SINGLE_ARG ) {
				for (Object obj : this.generated) {
					Iterator<?> it = ((Collection<?>) obj).iterator();
					this.iteratorsVector.add(it);
				}		
			}

		}
	}

	/**
	 * Per user execution of method 
	 * @param index Use generated_data[index] as arguments storage 
	 * @return false if done with the data, true if can run some more.
	 */

	public boolean execute(int index) {
		testAndInit();

		//the vector generated is not big enough
		if (this.generated.size() <= index) 
			return false;

		Iterator<?> it = this.iteratorsVector.elementAt(index); // ! we can't use here the main iterator

		if (it.hasNext() == false )
			return false;

		execOne(it.next());

		return true;

	}

	/**
	 * Execute the method using all the generated data 
	 */
	@Override
	public boolean execute() {
		testAndInit();

		if (!this.iterator.hasNext())
			return false;

		execOne(this.iterator.next());
		return true;
	}

	/**
	 * Setter for the method
	 * @param method
	 *            the method to set
	 */
	public void setMethod(Method method) {
		this.method = method;
		setArgsType();
	}


	/**
	 * Makes the decision regarding the arguments typing
	 */
	private void setArgsType() {
		boolean fvectorFlag = false;

		if (this.method != null )
			fvectorFlag = this.method.getParameterTypes()[0].equals(fVector.class);

		if (this.instance == null) {// static case:
			if (this.method != null && this.method.getParameterTypes().length > 1) //multiple arguments
				this.inTargetType = Cases.STATIC_MULT_ARG;
			else {
				if (fvectorFlag)
					this.inTargetType = Cases.STATIC_SINGLE_VECTOR;
				else
					this.inTargetType = Cases.STATIC_SINGLE_ARG;
			}
		}
		else { // users case:
			if (this.method != null && this.method.getParameterTypes().length > 1) //multiple arguments
				this.inTargetType = Cases.OBJ_MULT_ARG;
			else {
				if (fvectorFlag)
					this.inTargetType = Cases.OBJ_SINGLE_VECTOR;
				else
					this.inTargetType = Cases.OBJ_SINGLE_ARG;
			}
		}

	}

	/**
	 * Setter for the object to run on
	 * @param instance
	 *            the instance to run on
	 */
	public void setInstance(Object instance) {
		this.instance = instance;
		setArgsType();
	}

	/**
	 * Setter for the generator
	 * @param gen
	 *            the generator to use for arguments generation
	 */
	public void setGen(GeneratorInterface<?> gen) {
		this.gen = gen;
	}

	@Override
	public void setFs(LoggingInterface fs) {
		this.fs = fs;
	}

	@Override
	public void setProgresser(ProgressKeeperInterface pk) {
		//Auto-generated method stub

	}

	private Object[] convertToFVector(Object obj) {
		fVector fv = new fVector();
		Collection<?> col = (Collection<?>) obj;
		for (Object ob : col) 
			fv.add(ob);
		Object[] result = { fv };
		return result;
	}
}